#include "stdafx.h"
#include <MyFC/ScrollBox.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

const int SCROLL_LINE = 5;

// ScrollBox::ScrollBar

ScrollBox::ScrollBar::ScrollBar(ScrollBox & owner) :
	m_Owner(owner),
	m_Enabled(true),
	m_AlwaysVisible(false)
{
}

void ScrollBox::ScrollBar::Enable(bool on)
{
	if (on != m_Enabled) {
		m_Enabled = on;
		m_Owner.UpdateScrollBars();
	}
}

void ScrollBox::ScrollBar::SetAlwaysVisible(bool on)
{
	if (on != m_AlwaysVisible) {
		m_AlwaysVisible = on;
		m_Owner.UpdateScrollBars();
	}
}

BEGIN_MESSAGE_MAP(ScrollBox, CustomWindow<CWnd>)
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
END_MESSAGE_MAP()

ScrollBox::ScrollBox() :
	m_Horz(* this),
	m_Vert(* this)
{
	m_Preferred = CSize(0, 0);
	m_Alignment = ALIGN_NONE;
	m_SBUpdate = false;

	m_Offset = CPoint(- INT_MAX, - INT_MAX);
	m_Class = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS);
	m_Style = WS_VISIBLE|WS_CHILD|WS_HSCROLL|WS_VSCROLL|WS_CLIPCHILDREN|WS_CLIPSIBLINGS;
}

void ScrollBox::SetOffset(CPoint p)
{
	if (m_hWnd == 0)
		return;

	CSize cs = CalcClientSize();
	CSize ps = GetContentSize();

	if (cs.cx > ps.cx) {
		switch (m_Alignment & ALIGN_HMASK) {
		case ALIGN_HRIGHT:
			p.x = ps.cx - cs.cx;
			break;
		case ALIGN_HCENTER:
			p.x = (ps.cx - cs.cx) / 2;
			break;
		default:
			p.x = 0;
		}
	} else {
		if (p.x < 0)
			p.x = 0;
		if (p.x > ps.cx - cs.cx)
			p.x = ps.cx - cs.cx;
	}
	if (cs.cy > ps.cy)
		switch (m_Alignment & ALIGN_VMASK) {
		case ALIGN_VBOTTOM:
			p.y = ps.cy - cs.cy;
			break;
		case ALIGN_VCENTER:
			p.y = (ps.cy - cs.cy) / 2;
			break;
		default:
			p.y = 0;
		}
	else {
		if (p.y < 0)
			p.y = 0;
		if (p.y > ps.cy - cs.cy)
			p.y = ps.cy - cs.cy;
	}
	if (p != m_Offset) {
		m_Offset = p;
		LayoutControls();
		UpdateScrollBars();
		Invalidate();
	}
}

void ScrollBox::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
{
	CSize ps = GetContentSize();
	CSize cs = ClientSize();
	CPoint p = m_Offset;

	switch (nSBCode) {
	case SB_LEFT:
		p.x = 0;
		break;
	case SB_ENDSCROLL:
		break;
	case SB_LINELEFT:
		p.x -= SCROLL_LINE;
		break;
	case SB_LINERIGHT:
		p.x += SCROLL_LINE;
		break;
	case SB_PAGELEFT:
		p.x -= cs.cx;
		break;
	case SB_PAGERIGHT:
		p.x += cs.cx;
		break;
	case SB_RIGHT:
		p.x = ps.cx - cs.cx;
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		p.x = nPos;
		break;
	default:
		return;
	}
	SetOffset(p);
}

void ScrollBox::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
{
	CSize ps = GetContentSize();
	CSize cs = ClientSize();
	CPoint p = m_Offset;

	switch (nSBCode) {
	case SB_TOP:
		p.y = 0;
		break;
	case SB_ENDSCROLL:
		break;
	case SB_LINEUP:
		p.y -= SCROLL_LINE;
		break;
	case SB_LINEDOWN:
		p.y += SCROLL_LINE;
		break;
	case SB_PAGEUP:
		p.y -= cs.cy;
		break;
	case SB_PAGEDOWN:
		p.y += cs.cy;
		break;
	case SB_BOTTOM:
		p.y = ps.cy - cs.cy;
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		p.y = nPos;
		break;
	default:
		return;
	}
	SetOffset(p);
}

void ScrollBox::UpdateBounds()
{
	if (m_SBUpdate)
		return;
	CustomWindow<CWnd>::UpdateBounds();
	SetOffset(m_Offset);
	UpdateScrollBars();
}

void ScrollBox::UpdateScrollBars() 
{
	if (m_hWnd == 0)
		return;
	m_SBUpdate = true;

	SCROLLINFO info;
	info.cbSize = sizeof(SCROLLINFO);

	CSize cs = CalcClientSize();
	CSize ps = GetContentSize();

	if (cs.cx == ps.cx)
		cs.cx++;
	if (cs.cy == ps.cy)
		cs.cy++;
	if (ps.cx == 0)
		ps.cx = 1;
	if (ps.cy == 0)
		ps.cy = 1;

	info.fMask = SIF_PAGE|SIF_POS|SIF_RANGE;
	if (m_Horz.IsAlwaysVisible())
		info.fMask |= SIF_DISABLENOSCROLL;
	info.nMin	= 0;
	info.nMax	= ps.cx;
	info.nPage	= cs.cx;
	if (m_Offset.x > 0)
		info.nPos = m_Offset.x;
	else
		info.nPos = 0;
	VERIFY(SetScrollInfo(SB_HORZ, &info, TRUE));
//	VERIFY(GetScrollInfo(SB_HORZ, &info));

	info.fMask = SIF_PAGE|SIF_POS|SIF_RANGE;
	if (m_Vert.IsAlwaysVisible())
		info.fMask |= SIF_DISABLENOSCROLL;
	info.nMin	= 0;
	info.nMax	= ps.cy;
	info.nPage	= cs.cy;
	if (m_Offset.y > 0)
		info.nPos = m_Offset.y;
	else
		info.nPos = 0;
	VERIFY(SetScrollInfo(SB_VERT, &info, TRUE));
//	VERIFY(GetScrollInfo(SB_VERT, &info));

	m_SBUpdate = false;
}

CSize ScrollBox::GetMaxClientSize() const
{
	CSize s = ClientSize();

	int cx = GetSystemMetrics(SM_CYHSCROLL);
	int cy = GetSystemMetrics(SM_CXVSCROLL);

	SCROLLINFO info;
	info.cbSize = sizeof(SCROLLINFO);

	const_cast<ScrollBox &>(* this).GetScrollInfo(SB_HORZ, &info);
	if (m_Horz.IsAlwaysVisible() || (info.nPage != 0 && int(info.nPage) < info.nMax - info.nMin))
		s.cy += cy;
	const_cast<ScrollBox &>(* this).GetScrollInfo(SB_VERT, &info);
	if (m_Vert.IsAlwaysVisible() || (info.nPage != 0 && int(info.nPage) < info.nMax - info.nMin))
		s.cx += cx;

	return s;
}

CSize ScrollBox::GetContentSize() const
{
	CSize ps = m_Preferred;
	CSize cs = CalcClientSize();
	if (ps.cx < cs.cx) {
		if ((m_Alignment & ALIGN_HMASK) == ALIGN_HSTRETCH)
			ps.cx = cs.cx;
	} else {
		if (!m_Horz.IsEnabled())
			ps.cx = cs.cx;
	}
	if (ps.cy < cs.cy) {
		if ((m_Alignment & ALIGN_VMASK) == ALIGN_VSTRETCH)
			ps.cy = cs.cy;
	} else {
		if (!m_Vert.IsEnabled())
			ps.cy = cs.cy;
	}
	return ps;
}

CSize ScrollBox::CalcClientSize() const
{
	CSize cs = GetMaxClientSize();
	CSize ps = m_Preferred;

	int cx = GetSystemMetrics(SM_CXVSCROLL);
	int cy = GetSystemMetrics(SM_CYHSCROLL);
	CSize cmax = cs;
	CSize cmin = cmax - CSize(cx, cy);

	int bHorz, bVert;
	bHorz = bVert = 2; // 2 means "don't know"

	if (m_Horz.IsAlwaysVisible()) {
		bHorz = 1;
		cmax.cy -= cy;
	}
	if (!m_Horz.IsEnabled()) {
		bHorz = 0;
		cmin.cy += cy;
	}
	if (m_Vert.IsAlwaysVisible()) {
		bVert = 1;
		cmax.cx -= cx;
	}
	if (!m_Vert.IsEnabled()) {
		bVert = 0;
		cmin.cx += cx;
	}

	if (ps.cx <= cmax.cx && ps.cy <= cmax.cy) {
		if (bHorz == 2)
			bHorz = 0;
		if (bVert == 2)
			bVert = 0;
	} else {
		if (bHorz == 2)
			bHorz = ps.cx > cmin.cx ? 1 : 0;
		if (bVert == 2)
			bVert = ps.cy > cmin.cy ? 1 : 0;
	}
	if (bHorz == 1)
		cs.cy -= cy;
	if (bVert == 1)
		cs.cx -= cx;
	return cs;
}

void ScrollBox::HandleWindowCreate()
{
	CustomWindow<CWnd>::HandleWindowCreate();

	SCROLLINFO info;
	memset(&info, 0, sizeof(SCROLLINFO));
	info.cbSize = sizeof(SCROLLINFO);
	info.fMask = SIF_PAGE|SIF_POS|SIF_RANGE;
	info.nMin = 0;
	info.nMax = 1;
	info.nPos = 0;
	info.nPage = m_Horz.IsAlwaysVisible() ? 0 : 2;
	VERIFY(SetScrollInfo(SB_HORZ, &info, TRUE));
	info.nPage = m_Vert.IsAlwaysVisible() ? 0 : 2;
	VERIFY(SetScrollInfo(SB_VERT, &info, TRUE));
}

void ScrollBox::SetAlignment(int a)
{
	if (a != m_Alignment) {
		m_Alignment = a;
		SetOffset(m_Offset);
	}
}

void ScrollBox::SetPreferredContentSize(CSize s)
{
	if (m_Preferred != s) {
		m_Preferred = s;
		SetOffset(m_Offset);
		UpdateScrollBars();
		LayoutControls();
	}
}

// WidgetScrollBox

WidgetScrollBox::WidgetScrollBox() :
	m_pWidget(0)
{
}

void WidgetScrollBox::SetWidget(Widget * pWidget)
{
	if (m_pWidget != pWidget) {
		m_pWidget = pWidget;
		Add(m_pWidget);
		SetPreferredContentSize(m_pWidget->GetPreferredSize());
	}
}

void WidgetScrollBox::HandlePaint(CDC * pDC, const CRect & r)
{
	CBrush b;
	b.CreateSysColorBrush(COLOR_BTNFACE);
	pDC->FillRect(&r, &b);
}

void WidgetScrollBox::LayoutControls()
{
	if (m_pWidget != 0)
		m_pWidget->SetBounds(CRect(- GetOffset(), GetContentSize()));
}

} // My
